package com.quwan.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.quwan.anno.RecordLog;
import com.quwan.common.enums.RedisListenerEnum;
import com.quwan.common.enums.RequestUriEnum;
import com.quwan.constans.CacheKey;
import com.quwan.entity.TMember;
import com.quwan.entity.TSecurityAuditLog;
import com.quwan.mapper.TMemberMapper;
import com.quwan.mapper.TSecurityAuditLogMapper;
import com.quwan.utils.DateUtils;
import com.quwan.utils.redis.Redis;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.swing.text.html.Option;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@SuppressWarnings("Duplicates")
@Slf4j
@Component
@Aspect
public class RequestLog {
    @Autowired
    private TSecurityAuditLogMapper logMapper;
    @Autowired
    private TMemberMapper memberMapper;
    @Autowired
    private Redis redis;


    /**
     * 接口访问量统计
     * @param joinPoint
     */
    @Before("@annotation(com.quwan.anno.AccessCount)")
    public void doBeforeAccessCount(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            String timeKey = LocalDate.now().toString();
            HttpServletRequest request = attributes.getRequest();
            String url = request.getRequestURI().replace(request.getContextPath(), "");
            RequestUriEnum uriEnum = RequestUriEnum.getByUri(url);
            if (uriEnum!=null){
                //过期key设置
                String expireKey = RedisListenerEnum.auto_access_count.getKey()+RedisListenerEnum.split+ timeKey;
                if (!redis.exists(expireKey)&&
                        LocalDateTime.now().isBefore(LocalDateTime.of(LocalDate.now(),LocalTime.of(23,50)))) {
                    Long second = DateUtils.computeSecond(LocalDateTime.now(), LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 55)));
                    redis.setListener(expireKey,0,second);
                }
                //缓存接口访问次数
                redis.hIncrBy(CacheKey.DATA.accessCount+timeKey,Optional.ofNullable(RequestUriEnum.getByUri(url)).orElse(RequestUriEnum.DEF).getType(),1);

            }
        }
    }
    /**
     * 用户活跃时间修改
     * @param joinPoint
     */
    @Before("@annotation(com.quwan.anno.UpdateAccessTime)")
    public void doBeforeUpdateAccessTime(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            Map<String, String> map = new HashMap<>();
            Enumeration<?> temp = request.getAttributeNames();
            if (null != temp) {
                while (temp.hasMoreElements()) {
                    String en = (String) temp.nextElement();
                    String value = request.getAttribute(en).toString();
                    map.put(en, value);
                }
            }
            String mId = map.get("userId");

            if (org.apache.commons.lang3.StringUtils.isNotBlank(mId))
                memberMapper.update(null,
                        new LambdaUpdateWrapper<TMember>()
                                .set(TMember::getLastVisitTime, LocalDateTime.now())
                                .eq(TMember::getId, mId)
                );
        }

    }

    /**
     * 用户操作日志 记录
     *
     * @param joinPoint
     * @param recordLog
     */
    @Before("@annotation(com.quwan.anno.RecordLog)&&@annotation(recordLog)")
    public void doBeforeRecordLog(JoinPoint joinPoint, RecordLog recordLog) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            Map<String, Object> map = getRequestParamsByProceedingJoinPoint(joinPoint);

            logMapper.insert(new TSecurityAuditLog()
                    .setCreateTime(LocalDateTime.now())
                    .setRequestIp(getIp2(request))
                    .setRequestUrl(request.getRequestURI())
                    .setRequestParam(JSONObject.toJSONString(map))
                    .setUserId(Long.parseLong(request.getAttribute("userId") + ""))
                    .setRequestPlatform(request.getHeader("source") != null ? Byte.parseByte(request.getHeader("source")) : 0)
                    .setMobileType(request.getHeader("device"))
                    .setType(recordLog.value().getType())
                    .setAppVersion(request.getHeader("v")));
        }

    }


    @Pointcut("execution(public * com.quwan.app.controller..*(..)) || execution(public * com.quwan.manager.controller..*(..))")
    public void requestServer() {
    }

    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            Map<String, Object> map = getRequestParamsByProceedingJoinPoint(proceedingJoinPoint);
            RequestInfo requestInfo = new RequestInfo()
                    .setIp(getIp2(request))
                    .setUrl(request.getRequestURI())
                    .setHttpMethod(request.getMethod())
                    .setRequestParams(map)
                    .setResult(result)
                    .setAttrs(getRequestAttrs(request))
                    .setTimeCost(System.currentTimeMillis() - start);
            log.info("Request Info {}", JSONObject.toJSONString(requestInfo));
        }
        return result;
    }


    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            RequestErrorInfo requestErrorInfo = new RequestErrorInfo()
                    .setIp(getIp2(request))
                    .setUrl(request.getRequestURL().toString())
                    .setHttpMethod(request.getMethod())
                    .setRequestParams(getRequestParamsByJoinPoint(joinPoint))
                    .setAttrs(getRequestAttrs(request))
                    .setException(e.toString());
            log.error("Error Info : {}", JSONObject.toJSONString(requestErrorInfo));
        }
    }

    /**
     * 获取入参
     *
     * @param proceedingJoinPoint
     * @return
     */
    private Map<String, Object> getRequestParamsByProceedingJoinPoint(JoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];

            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }
            if ("request".equalsIgnoreCase(paramNames[i]) || "response".equalsIgnoreCase(paramNames[i]) || value instanceof ServletRequest || value instanceof ServletResponse) {
                continue;
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

    private Map<String, Object> getRequestAttrs(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("username", request.getAttribute("username"));
        map.put("module", request.getAttribute("module"));
        return map;
    }

    @Data
    @Accessors(chain = true)
    public class RequestInfo {
        @JSONField(ordinal = 1)
        private String ip;
        @JSONField(ordinal = 2)
        private String url;
        @JSONField(ordinal = 3)
        private Object attrs;
        @JSONField(ordinal = 3)
        private Object requestParams;
        @JSONField(ordinal = 4)
        private String httpMethod;
        @JSONField(ordinal = 5)
        private Object result;
        @JSONField(ordinal = 6)
        private Long timeCost;
    }

    @Data
    @Accessors(chain = true)
    public class RequestErrorInfo {
        @JSONField(ordinal = 1)
        private String ip;
        @JSONField(ordinal = 2)
        private String url;
        @JSONField(ordinal = 3)
        private Object attrs;
        @JSONField(ordinal = 3)
        private Object requestParams;
        @JSONField(ordinal = 4)
        private String httpMethod;
        @JSONField(ordinal = 5)
        private String exception;
    }

    public String getIp2(HttpServletRequest request) {
        // 基于 X-Forwarded-For 获得
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.hasText(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值，第一个 ip 才是真实 ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        // 基于 X-Real-IP 获得
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.hasText(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        // 默认方式
        return request.getRemoteAddr();
    }


    /**
     * 获取ip
     */
    private String getIp(HttpServletRequest request) {
        List<String> ipHeadList = Stream.of("X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "X-Real-IP").collect(Collectors.toList());
        for (String ipHead : ipHeadList) {
            if (checkIP(request.getHeader(ipHead))) {
                return request.getHeader(ipHead).split(",")[0];
            }
        }
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            return "0:0:0:0:0:0:0:1".equals(request.getRemoteAddr()) ? "127.0.0.1" : request.getRemoteAddr();
        }
    }

    /**
     * 检查ip存在
     */
    private boolean checkIP(String ip) {
        return !(null == ip || 0 == ip.length() || "unknown".equalsIgnoreCase(ip));
    }


}